# koroFileHeader at Yungoal acer
# Create: 2022-10-19 17:01:13
# LastEdit: 2022-10-19 17:18:32
"""缓存"""
__author__ = '749B'

import functools
import os.path

from django.core.cache.backends.filebased import FileBasedCache
from django.core.cache import caches

import logging
logger = logging.getLogger(__name__)
logger.success = lambda msg, *args, **kwargs: logger.log(25, msg, *args, **kwargs)  # type: ignore


def cache_auto_key(user_function=None, *, cache_key='default', timeout=None, version=None):
    """自动生成缓存的key
    参考缓存的key也用冒号做分隔，分隔模块名和函数名。函数名和参数部分模块调用的形式用括号
    假设模块是azure_cloud.views，函数调用是user_function(arg1, arg2, key3='value3', key4='value4')
    生成的key就是 azure_cloud.views:user_function(arg1,arg2,/,*,key3=value3,key4=value4)
    """
    if user_function is None:
        return functools.partial(cache_auto_key, cache_key=cache_key, timeout=timeout, version=version)

    cache = caches[cache_key]
    def _make_key(user_function, args, kwds, kwd_mark = ('[]',)) -> str:
        """生成缓存的key"""
        key = args  # 这个是元组，后面的添加数据也都是添加元组类型
        if kwds:
            key += kwd_mark
            for k, v in kwds.items():
                key += (f'{k}={v}',)
        key_str = ','.join([str(k) for k in key])
        return f'{user_function.__module__}:{user_function.__name__}({key_str})'

    @functools.wraps(user_function)
    def wrapper(*args, **kwds):
        def user_function_with_args():
            """cache 的 get_or_set 方法传入默认值时，要传入可调用对象而不是调用结果
            这样在传参的时候不会执行，等到缓存未命中才会去执行拿结果。
            否则会在传参的时候先执行一遍，在进入get_or_set内部判断缓存是否命中前就先执行了一遍整个调用。
            """
            return user_function(*args, **kwds)
        
        key = _make_key(user_function, args, kwds)
        if timeout:
            res = cache.get_or_set(key, user_function_with_args, timeout, version=version)
        else:
            res = cache.get_or_set(key, user_function_with_args, version=version)
        return res
    return wrapper


class FileBasedCacheNoHash(FileBasedCache):
    """使用自定义的 _key_to_file 方法生成可读性缓存文件，可以按需将文件删除，清理掉指定的缓存
    自定义的方法保证的文件名可读，但是无法保证生成的所有key都是合法的文件名。
    只能对自己定义的函数在使用cache_auto_key做缓存的时候使用，其他时候还是用默认的FileBasedCache。
    这需要建立两个缓存，默认的default使用更通用的FileBasedCache，
    对于cache_auto_key装饰器装饰的自定义的函数的缓存使用FileBasedCacheNoHash，能保证生成的key是合法文件名
    """

    def _key_to_file(self, key, version=None):
        key = self.make_and_validate_key(key, version=version)
        key = key.replace(':', '-')
        return os.path.join(
            self._dir,
            "".join([key, self.cache_suffix]),
        )

